﻿using System;
using System.Collections.Generic;
using QuickGraph;
using QuickGraph.Predicates;
using QuickGraph.Algorithms.RankedShortestPath;

namespace SystemWnioskujacy
{
    public class GraphBase
    {
        private BidirectionalGraph<string, TaggedEdge<string, EdgeTag>> Graph = new BidirectionalGraph<string, TaggedEdge<string, EdgeTag>>();
        private const int MinID = 0;
        public int NID = MinID;

        public bool Add(string Fact, out int ID)
        {
            ID = MinID - 1;
            if (String.IsNullOrWhiteSpace(Fact)) return false;
            if (Exists(Fact))
            {
                ID = GetID(Fact);
                return false;
            }

            string[] Words = Fact.Split(' ');
            Graph.AddVerticesAndEdge(new TaggedEdge<string, EdgeTag>(String.Empty, Words[0], new EdgeTag(NID, 0)));
            for (int i = 0, j = 1; j < Words.Length; i++, j++) Graph.AddVerticesAndEdge(new TaggedEdge<string, EdgeTag>(Words[i], Words[j], new EdgeTag(NID, j)));
            ID = NID++;
            return true;
        }

        public bool Remove(string Fact, out int ID)
        {
            ID = MinID - 1;
            if (String.IsNullOrWhiteSpace(Fact) || !Exists(Fact)) return false;
            ID = GetID(Fact);

            return Remove(ID);
        }

        public bool Remove(int ID)
        {
            if (ID >= MinID)
            {
                if (!Exists(ID)) return false;
                Graph.RemoveEdgeIf(e => e.Tag.ID == ID);
                Graph.RemoveVertexIf(v => Graph.IsInEdgesEmpty(v) && Graph.IsOutEdgesEmpty(v) && v != String.Empty);
                return true;
            }
            else return false;
        }

        public void Clear()
        {
            Graph.Clear();
        }

        public int GetID(string Fact)
        {
            if (String.IsNullOrWhiteSpace(Fact)) return MinID - 1;

            string[] Words = Fact.Split(' ');
            var FG = new FilteredBidirectionalGraph<string, TaggedEdge<string, EdgeTag>, BidirectionalGraph<string, TaggedEdge<string, EdgeTag>>>(Graph, v => Array.IndexOf(Words, v) >= 0 || v == String.Empty, e => true);

            IEnumerable<TaggedEdge<string, EdgeTag>> SEs;
            if (FG.TryGetEdges(String.Empty, Words[0], out SEs))
            {
                foreach (TaggedEdge<string, EdgeTag> SE in SEs)
                {
                    var FE = new FilteredVertexAndEdgeListGraph<string, TaggedEdge<string, EdgeTag>, FilteredBidirectionalGraph<string, TaggedEdge<string, EdgeTag>, BidirectionalGraph<string, TaggedEdge<string, EdgeTag>>>>(FG, v => true, e => e.Tag.ID == SE.Tag.ID);

                    if (FE.EdgeCount != Words.Length) continue;
                    bool Found = true;

                    for (int i = 0, j = 1; j < Words.Length; i++, j++)
                    {
                        IEnumerable<TaggedEdge<string, EdgeTag>> CEs;
                        if (!FE.TryGetEdges(Words[i], Words[j], out CEs))
                        {
                            Found = false;
                        }
                        else
                        {
                            Found = false;

                            foreach (TaggedEdge<string, EdgeTag> CE in CEs)
                            {
                                if (CE.Tag.Position == j)
                                {
                                    Found = true;
                                    break;
                                }
                            }
                        }

                        if (!Found) break;
                    }

                    if (Found) return SE.Tag.ID;
                }
            }

            return MinID - 1;
        }

        public bool Exists(string Fact)
        {
            if (GetID(Fact) >= MinID) return true;
            else return false;
        }

        public bool Exists(int ID)
        {
            var R = new FilteredEdgeListGraph<string, TaggedEdge<string, EdgeTag>, BidirectionalGraph<string, TaggedEdge<string, EdgeTag>>>(Graph, v => true, e => e.Tag.ID == ID);

            if (R.EdgeCount > 0) return true;
            else return false;
        }

        public List<string> GetAll()
        {
            List<string> Result = new List<string>();

            for (int i = MinID; i < NID; i++)
            {
                var CFs = new FilteredEdgeListGraph<string, TaggedEdge<string, EdgeTag>, BidirectionalGraph<string, TaggedEdge<string, EdgeTag>>>(Graph, v => true, e => e.Tag.ID == i);
                string CF = String.Empty;

                for (int j = 0; j < CFs.EdgeCount; j++)
                {
                    var CE = new FilteredEdgeListGraph<string, TaggedEdge<string, EdgeTag>, FilteredEdgeListGraph<string, TaggedEdge<string, EdgeTag>, BidirectionalGraph<string, TaggedEdge<string, EdgeTag>>>>(CFs, v => true, e => e.Tag.Position == j);
                    foreach (TaggedEdge<string, EdgeTag> E in CE.Edges)
                    {
                        if (j == CFs.EdgeCount - 1) CF += E.Target;
                        else CF += E.Target + " ";
                    }
                }

                Result.Add(CF);
            }

            return Result;
        }

        private bool Execute(Query Q, FilteredVertexAndEdgeListGraph<string, TaggedEdge<string, EdgeTag>, BidirectionalGraph<string, TaggedEdge<string, EdgeTag>>> FE)
        {
            string[] Q1 = new string[] {String.Empty};
            string[] Q2;
            bool LastOut = false;

            for (int i = 0; i < Q.Length; i++)
            {
                if (Q[i].Length == 0)
                {
                    LastOut = true;
                    continue;
                }
                else
                {
                    Q2 = Q[i].Get();
                    FilteredVertexAndEdgeListGraph<string, TaggedEdge<string, EdgeTag>, FilteredVertexAndEdgeListGraph<string, TaggedEdge<string, EdgeTag>, BidirectionalGraph<string, TaggedEdge<string, EdgeTag>>>> tFE;

                    if (!LastOut)
                    {
                        tFE = new FilteredVertexAndEdgeListGraph<string, TaggedEdge<string, EdgeTag>, FilteredVertexAndEdgeListGraph<string, TaggedEdge<string, EdgeTag>, BidirectionalGraph<string, TaggedEdge<string, EdgeTag>>>>(FE, v => true, e => e.Tag.Position == i && Array.IndexOf(Q1, e.Source) >= 0 && Array.IndexOf(Q2, e.Target) >= 0);
                    }
                    else
                    {
                        tFE = new FilteredVertexAndEdgeListGraph<string, TaggedEdge<string, EdgeTag>, FilteredVertexAndEdgeListGraph<string, TaggedEdge<string, EdgeTag>, BidirectionalGraph<string, TaggedEdge<string, EdgeTag>>>>(FE, v => true, e => e.Tag.Position == i && Array.IndexOf(Q2, e.Target) >= 0);
                    }

                    if (tFE.EdgeCount == 0) return false;
                    Q1 = Q2;
                    LastOut = false;
                }
            }

            return true;
        }

        public bool Execute(Query Q)
        {
            List<int> R = new List<int>();
            IEnumerable<TaggedEdge<string, EdgeTag>> Es;

            if ((!Q[0].IsVar && Graph.TryGetEdges(String.Empty, Q[0][0], out Es)) || (Q[0].IsVar && Graph.TryGetOutEdges(String.Empty, out Es)))
            {
                foreach (TaggedEdge<string, EdgeTag> CE in Es)
                {
                    var FE = new FilteredVertexAndEdgeListGraph<string, TaggedEdge<string, EdgeTag>, BidirectionalGraph<string, TaggedEdge<string, EdgeTag>>>(Graph, v => true, e => e.Tag.ID == CE.Tag.ID);

                    if (FE.EdgeCount != Q.Length) continue;
                    if (Execute(Q, FE)) R.Add(CE.Tag.ID);
                }

                if (R.Count == 0) return false;
                else
                {
                    SetResult(Q, R);
                    Q.IDs = new int[R.Count];
                    R.CopyTo(Q.IDs);
                    for (int i = 0; i < Q.Length; i++) if (Q[i].IsVar) { Q[i].IDs = new int[R.Count]; R.CopyTo(Q[i].IDs); }
                    return true;
                }
            }

            return false;
        }

        public bool Execute2(Query Q, Rule The_Rule)
        {
            List<int> R = new List<int>();
            IEnumerable<TaggedEdge<string, EdgeTag>> Es;

            if ((!Q[0].IsVar && Graph.TryGetEdges(String.Empty, Q[0][0], out Es)) || (Q[0].IsVar && Graph.TryGetOutEdges(String.Empty, out Es)))
            {
                foreach (TaggedEdge<string, EdgeTag> CE in Es)
                {
                    if (Base.Get_IRF(CE.Tag.ID) < Base.RulesBase.r2 && Base.Get_IRF(CE.Tag.ID) != 0.0) continue;
                    var FE = new FilteredVertexAndEdgeListGraph<string, TaggedEdge<string, EdgeTag>, BidirectionalGraph<string, TaggedEdge<string, EdgeTag>>>(Graph, v => true, e => e.Tag.ID == CE.Tag.ID);

                    if (FE.EdgeCount != Q.Length) continue;
                    if (Execute(Q, FE)) R.Add(CE.Tag.ID);
                }

                if (R.Count == 0) return false;
                else
                {
                    SetResult(Q, R);
                    Q.IDs = new int[R.Count];
                    R.CopyTo(Q.IDs);
                    for (int i = 0; i < Q.Length; i++) if (Q[i].IsVar) { Q[i].IDs = new int[R.Count]; R.CopyTo(Q[i].IDs); }

                    The_Rule.n_grf = Math.Min(The_Rule.n_grf, Base.Get_GRF(R[0]));
                    if (Base.Get_IRF(R[0]) != 0) The_Rule.n_irf = Math.Min(The_Rule.n_irf, Base.Get_IRF(R[0]));

                    return true;
                }
            }

            return false;
        }

        private void SetResult(Query Q, List<int> R)
        {
            Q.ClearVariables();
            var RFs = new FilteredEdgeListGraph<string, TaggedEdge<string, EdgeTag>, BidirectionalGraph<string, TaggedEdge<string, EdgeTag>>>(Graph, v => true, e => R.Contains(e.Tag.ID));

            foreach (int i in R)
            {
                var FEs = new FilteredEdgeListGraph<string, TaggedEdge<string, EdgeTag>, FilteredEdgeListGraph<string, TaggedEdge<string, EdgeTag>, BidirectionalGraph<string, TaggedEdge<string, EdgeTag>>>>(RFs, v => true, e => e.Tag.ID == i);

                foreach (TaggedEdge<string, EdgeTag> E in FEs.Edges)
                {
                    if (Q[E.Tag.Position].IsVar) Q[E.Tag.Position].AddValue(E.Target);
                }
            }
        }

        public string[][] Results(Query Q1, Query Q2, QueryParameter QP1, QueryParameter QP2, int D)
        {
            string[][] R = new string[2][];
            for (int i = 0; i < R.Length; i++) R[i] = new string[0];

            var FG = new FilteredBidirectionalGraph<string, TaggedEdge<string, EdgeTag>, BidirectionalGraph<string, TaggedEdge<string, EdgeTag>>>(Graph, v => true, e => Array.IndexOf(Q1.IDs, e.Tag.ID) >= 0 || Array.IndexOf(Q2.IDs, e.Tag.ID) >= 0);
            var PathFinder = new HoffmanPavleyRankedShortestPathAlgorithm<string, TaggedEdge<string, EdgeTag>>(FG, E => 1.0);
            PathFinder.ShortestPathCount = Math.Abs(D);

            for (int i = 0; i < QP1.Length; i++)
            {
                for (int j = 0; j < QP2.Length; j++)
                {
                    if (Array.IndexOf(QP1.Get(), QP1[i]) < i) continue;
                    if (D > 0)
                    {
                        PathFinder.SetRootVertex(QP1[i]);
                        PathFinder.SetGoalVertex(QP2[j]);
                    }
                    else
                    {
                        PathFinder.SetGoalVertex(QP1[i]);
                        PathFinder.SetRootVertex(QP2[j]);
                    }
                    PathFinder.Compute();

                    if (PathFinder.ComputedShortestPathCount > 0)
                    {
                        foreach (IEnumerable<TaggedEdge<string, EdgeTag>> Es in PathFinder.ComputedShortestPaths)
                        {
                            if (new List<TaggedEdge<string, EdgeTag>>(Es).Count == Math.Abs(D))
                            {
                                Array.Resize(ref R[0], R[0].Length + 1);
                                Array.Resize(ref R[1], R[1].Length + 1);
                                R[0][R[0].Length - 1] = QP1[i];
                                R[1][R[1].Length - 1] = QP2[j];
                                break;
                            }
                        }
                    }
                }
            }

            return R;
        }
    }
}
